﻿namespace DCHealthCheck.Common {

    #region Usings
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks; 
    #endregion

    public class HierarchyNode<T> : IEnumerable<HierarchyNode<T>>, IEquatable<HierarchyNode<T>>, IEquatable<T> {

        public readonly IReadOnlyList<HierarchyNode<T>> ChildNodes;
        public readonly int Depth;
        public readonly Lazy<IReadOnlyList<HierarchyNode<T>>> Flattened;
        public bool HasChildren {
            get { return this.ChildNodes != null && ChildNodes.Any(); }
        }
        public readonly T Node;
        public HierarchyNode<T> Parent;

        public HierarchyNode(T node, IEnumerable<HierarchyNode<T>> childNodes, int depth) {
            if (node == null)
                throw new ArgumentNullException("node", "node cannot be null");
            if (depth < 0)
                throw new ArgumentOutOfRangeException("depth cannot be less than zero", "depth");

            this.Node = node;
            this.ChildNodes = (childNodes ?? Enumerable.Empty<HierarchyNode<T>>()).ToList();
            this.Depth = depth;
            this.Flattened = new Lazy<IReadOnlyList<HierarchyNode<T>>>(() => this.FlattenHierarchy().ToList(), true);

            foreach (HierarchyNode<T> child in this.ChildNodes) {
                child.Parent = this;
            }
        }

        public override bool Equals(object obj) {
            return Equals(obj as HierarchyNode<T>);
        }

        public bool Equals(HierarchyNode<T> other) {
            return other != null && Equals(other.Node);
        }

        public bool Equals(T other) {
            return this.Node.Equals(other);
        }

        private IEnumerable<HierarchyNode<T>> FlattenHierarchy() {
            yield return this;

            if (this.HasChildren) {
                foreach (HierarchyNode<T> child in this.ChildNodes) {
                    foreach (HierarchyNode<T> childsChild in child.FlattenHierarchy()) {
                        yield return childsChild;
                    }
                }
            }
        }

        public IEnumerator<HierarchyNode<T>> GetEnumerator() {
            return this.GetFlatChildren().GetEnumerator();
        }

        IEnumerator System.Collections.IEnumerable.GetEnumerator() {
            return this.GetFlatChildren().GetEnumerator();
        }

        public IReadOnlyList<HierarchyNode<T>> GetFlatChildren() {
            return this.Flattened.Value;
        }

        public IReadOnlyList<HierarchyNode<T>> GetParents() {
            var parents = new List<HierarchyNode<T>>();
            HierarchyNode<T> parent = this.Parent;

            while (parent != null) {
                parents.Add(parent);
                parent = parent.Parent;
            }

            parents.Reverse();

            return parents;
        }

        public override int GetHashCode() {
            return this.Node.GetHashCode();
        }

    }
}
